/*
   Copyright (c) 2016 VMware, Inc. All Rights Reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package job

import (
	"fmt"
	"sync"

	"github.com/vmware/harbor/src/common/dao"
	"github.com/vmware/harbor/src/common/models"
	uti "github.com/vmware/harbor/src/common/utils"
	"github.com/vmware/harbor/src/common/utils/log"
	"github.com/vmware/harbor/src/jobservice/config"
	"github.com/vmware/harbor/src/jobservice/replication"
	"github.com/vmware/harbor/src/jobservice/utils"
)

// RepJobParm wraps the parm of a job
type RepJobParm struct {
	LocalRegURL string
	//目标URL
	TargetURL string
	//目标Harbor用户名
	TargetUsername string
	//目标Harbor密码
	TargetPassword string
	//本地仓库
	Repository string

	Tags      []string
	Enabled   int
	Operation string
	Insecure  bool
}

// SM is the state machine to handle job, it handles one job at a time.
//定义状态机结构体
type SM struct {
	JobID         int64
	CurrentState  string
	PreviousState string
	//The states that don't have to exist in transition map, such as "Error", "Canceled"
	ForcedStates map[string]struct{}
	Transitions  map[string]map[string]struct{}
	Handlers     map[string]StateHandler
	desiredState string
	Logger       *log.Logger
	Parms        *RepJobParm
	lock         *sync.Mutex
}

// EnterState transit the statemachine from the current state to the state in parameter.
// It returns the next state the statemachine should tranit to.
//获取状态机的下一个状态
func (sm *SM) EnterState(s string) (string, error) {
	log.Debugf("Job id: %d, transiting from State: %s, to State: %s", sm.JobID, sm.CurrentState, s)
	//根据当前状态机的状态
	targets, ok := sm.Transitions[sm.CurrentState]
	//是否存在handler
	_, exist := targets[s]
	//ForcedStates[error,canceled,retrying,stop]
	_, isForced := sm.ForcedStates[s]
	//如果当前状态不在转换表并且不在ForcedStates map中表示没有找到与之对应的映射关系
	if !exist && !isForced {
		return "", fmt.Errorf("job id: %d, transition from %s to %s does not exist", sm.JobID, sm.CurrentState, s)
	}
	//在Handlers中查找是否存在handler,有部分状态是没有Handler的,如:pending状态
	exitHandler, ok := sm.Handlers[sm.CurrentState]
	if ok {
		//应该是预留接口,可以做一些收尾工作
		if err := exitHandler.Exit(); err != nil {
			return "", err
		}
	} else {
		log.Debugf("Job id: %d, no handler found for state:%s, skip", sm.JobID, sm.CurrentState)
	}
	//查找将要转换目标handler
	enterHandler, ok := sm.Handlers[s]
	//如果一个任务没有调度结束,也就是状态不是 stop,restring,cancel,unknow状态,那么都应该是continue状态
	var next = models.JobContinue
	var err error
	if ok {
		if next, err = enterHandler.Enter(); err != nil {
			return "", err
		}
	} else {
		log.Debugf("Job id: %d, no handler found for state:%s, skip", sm.JobID, s)
	}
	//把上一个状态改为当前状态
	sm.PreviousState = sm.CurrentState
	//把要转换的状态改为当前状态
	sm.CurrentState = s
	log.Debugf("Job id: %d, transition succeeded, current state: %s", sm.JobID, s)
	return next, nil
}

// Start kicks off the statemachine to transit from current state to s, and moves on
// It will search the transit map if the next state is "_continue", and
// will enter error state if there's more than one possible path when next state is "_continue"
//启动状态机
func (sm *SM) Start(s string) {
	//状态机启动时 n 应该为continue
	n, err := sm.EnterState(s)
	log.Debugf("Job id: %d, next state from handler: %s", sm.JobID, n)
	//如果不是final状态(stoped,finished,error), 那么n的值就是_continue,故len(n) > 0 && err == nil 永远为true,故for循环会调用后续的handler
	for len(n) > 0 && err == nil {
		//检查同步任务是否被停止,如果sm.getDesiredState == stoped 那么整个状态机将会被结束
		if d := sm.getDesiredState(); len(d) > 0 {
			log.Debugf("Job id: %d. Desired state: %s, will ignore the next state from handler", sm.JobID, d)
			//完成状态机的后续工作
			n = d
			sm.setDesiredState("")
			continue
		}
		//根据当前状态在tansitions table 中查找transition  len(sm.Transitions[sm.CurrentState]) == 1表示找到了
		if n == models.JobContinue && len(sm.Transitions[sm.CurrentState]) == 1 {
			//一直不解为何要遍历 sm.Transitions[sm.CurrentState]最后还break了?????????
			for n = range sm.Transitions[sm.CurrentState] {
				break
			}
			log.Debugf("Job id: %d, Continue to state: %s", sm.JobID, n)
			continue
		}
		//根据当前状态机的状态在tansitions table 中查找transition,如果找不到就证明没有对应的from状态,直接报错就行了
		if n == models.JobContinue && len(sm.Transitions[sm.CurrentState]) != 1 {
			log.Errorf("Job id: %d, next state is continue but there are %d possible next states in transition table", sm.JobID, len(sm.Transitions[sm.CurrentState]))
			err = fmt.Errorf("Unable to continue")
			break
		}
		//获取将要执行下一个状态机的状态
		//注意:这是一个循环
		n, err = sm.EnterState(n)
		log.Debugf("Job id: %d, next state from handler: %s", sm.JobID, n)
	}
	//获取状态失败,直接把job状态的状态改为JobError
	if err != nil {
		log.Warningf("Job id: %d, the statemachin will enter error state due to error: %v", sm.JobID, err)
		//发现实质上并没有用
		sm.EnterState(models.JobError)
	}
}

// AddTransition add a transition to the transition table of state machine, the handler is the handler of target state "to"
//添加一个装换关系到状态机当中
func (sm *SM) AddTransition(from string, to string, h StateHandler) {
	_, ok := sm.Transitions[from]
	if !ok {
		sm.Transitions[from] = make(map[string]struct{})
	}
	sm.Transitions[from][to] = struct{}{}
	sm.Handlers[to] = h
}

// RemoveTransition removes a transition from transition table of the state machine
//从状态机当中移除转换关系
func (sm *SM) RemoveTransition(from string, to string) {
	_, ok := sm.Transitions[from]
	if !ok {
		return
	}
	delete(sm.Transitions[from], to)
}

// Stop will set the desired state as "stopped" such that when next tranisition happen the state machine will stop handling the current job
// and the worker can release itself to the workerpool.
func (sm *SM) Stop(id int64) {
	log.Debugf("Trying to stop the job: %d", id)
	sm.lock.Lock()
	defer sm.lock.Unlock()
	//need to check if the sm switched to other job
	if id == sm.JobID {
		//如果Job同步任务被用户终止,那么整个状态机的生命周期将被停止
		sm.desiredState = models.JobStopped
		log.Debugf("Desired state of job %d is set to stopped", id)
	} else {
		log.Debugf("State machine has switched to job %d, so the action to stop job %d will be ignored", sm.JobID, id)
	}
}

func (sm *SM) getDesiredState() string {
	sm.lock.Lock()
	defer sm.lock.Unlock()
	return sm.desiredState
}

func (sm *SM) setDesiredState(s string) {
	sm.lock.Lock()
	defer sm.lock.Unlock()
	sm.desiredState = s
}

// Init initialzie the state machine, it will be called once in the lifecycle of state machine.
//初始化状态机
func (sm *SM) Init() {
	sm.lock = &sync.Mutex{}
	sm.Handlers = make(map[string]StateHandler)
	sm.Transitions = make(map[string]map[string]struct{})
	sm.ForcedStates = map[string]struct{}{
		models.JobError:    struct{}{},
		models.JobStopped:  struct{}{},
		models.JobCanceled: struct{}{},
		models.JobRetrying: struct{}{},
	}
}

// Reset resets the state machine so it will start handling another job.
//重置Job状态
func (sm *SM) Reset(jid int64) error {
	//To ensure the new jobID is visible to the thread to stop the SM
	sm.lock.Lock()
	sm.JobID = jid
	sm.desiredState = ""
	sm.lock.Unlock()

	sm.Logger = utils.NewLogger(sm.JobID)
	//init parms
	//获取数据库的Job信息
	job, err := dao.GetRepJob(sm.JobID)
	if err != nil {
		return fmt.Errorf("Failed to get job, error: %v", err)
	}
	if job == nil {
		return fmt.Errorf("The job doesn't exist in DB, job id: %d", sm.JobID)
	}
	//获取数据库镜像同步策略
	policy, err := dao.GetRepPolicy(job.PolicyID)
	if err != nil {
		return fmt.Errorf("Failed to get policy, error: %v", err)
	}
	if policy == nil {
		return fmt.Errorf("The policy doesn't exist in DB, policy id:%d", job.PolicyID)
	}
	sm.Parms = &RepJobParm{
		//docker registry 地址
		LocalRegURL: config.LocalRegURL(),
		//仓库名称
		Repository: job.Repository,
		//
		Tags: job.TagList,
		//是否启用镜像同步 1:启用 0:禁用
		Enabled: policy.Enabled,

		Operation: job.Operation,
		//是否验证证书
		Insecure: !config.VerifyRemoteCert(),
	}
	//如果policy.Enabled == 0 表示禁止镜像同步,直接return nil就好了
	if policy.Enabled == 0 {
		//worker will cancel this job
		return nil
	}
	//获取目标镜像仓库
	target, err := dao.GetRepTarget(policy.TargetID)
	if err != nil {
		return fmt.Errorf("Failed to get target, error: %v", err)
	}
	if target == nil {
		return fmt.Errorf("The target doesn't exist in DB, target id: %d", policy.TargetID)
	}
	sm.Parms.TargetURL = target.URL
	sm.Parms.TargetUsername = target.Username
	pwd := target.Password

	//解密用户密码
	//如果密码前缀是<enc-v1> 则采用aes解密，否则就是base64
	if len(pwd) != 0 {
		pwd, err = uti.ReversibleDecrypt(pwd, config.SecretKey())
		if err != nil {
			return fmt.Errorf("failed to decrypt password: %v", err)
		}
	}
	//获取用户的真是密码
	sm.Parms.TargetPassword = pwd

	//init states handlers
	//初始化Statehandler Map
	sm.Handlers = make(map[string]StateHandler)
	//初始化转换二级map
	sm.Transitions = make(map[string]map[string]struct{})
	//将当前Job设置成运行状态
	sm.CurrentState = models.JobPending
	//添加转换关系和对应的处理器,以下处理器都会修改数据库中的job状态
	sm.AddTransition(models.JobPending, models.JobRunning, StatusUpdater{sm.JobID, models.JobRunning})
	sm.AddTransition(models.JobRetrying, models.JobRunning, StatusUpdater{sm.JobID, models.JobRunning})
	sm.Handlers[models.JobError] = StatusUpdater{sm.JobID, models.JobError}
	sm.Handlers[models.JobStopped] = StatusUpdater{sm.JobID, models.JobStopped}
	sm.Handlers[models.JobRetrying] = Retry{sm.JobID}

	switch sm.Parms.Operation {
	//复制任务到远程的registry/harbor实例
	case models.RepOpTransfer:

		addImgTransferTransition(sm)
		//镜像删除
	case models.RepOpDelete:
		addImgDeleteTransition(sm)
	default:
		err = fmt.Errorf("unsupported operation: %s", sm.Parms.Operation)
	}

	return err
}

//for testing onlly
//测试
func addTestTransition(sm *SM) error {
	sm.AddTransition(models.JobRunning, "pull-img", ImgPuller{img: sm.Parms.Repository, logger: sm.Logger})
	return nil
}

//镜像同步
func addImgTransferTransition(sm *SM) {
	base := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
		sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
		sm.Parms.Insecure, sm.Parms.Tags, sm.Logger)

	//1.创建客户端
	sm.AddTransition(models.JobRunning, replication.StateInitialize, &replication.Initializer{BaseHandler: base})
	//2.检查本地project创建远端project
	sm.AddTransition(replication.StateInitialize, replication.StateCheck, &replication.Checker{BaseHandler: base})
	//3.获取mainfest，和远端比较分析需要同步的层
	sm.AddTransition(replication.StateCheck, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base})
	//4.传输分层
	sm.AddTransition(replication.StatePullManifest, replication.StateTransferBlob, &replication.BlobTransfer{BaseHandler: base})
	//这个是没有需要同步的镜像的时候停止运行。
	sm.AddTransition(replication.StatePullManifest, models.JobFinished, &StatusUpdater{sm.JobID, models.JobFinished})
	//推送manifest到远端
	sm.AddTransition(replication.StateTransferBlob, replication.StatePushManifest, &replication.ManifestPusher{BaseHandler: base})
	//
	sm.AddTransition(replication.StatePushManifest, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base})
}

//任务删除
func addImgDeleteTransition(sm *SM) {
	deleter := replication.NewDeleter(sm.Parms.Repository, sm.Parms.Tags, sm.Parms.TargetURL,
		sm.Parms.TargetUsername, sm.Parms.TargetPassword, sm.Parms.Insecure, sm.Logger)

	sm.AddTransition(models.JobRunning, replication.StateDelete, deleter)
	sm.AddTransition(replication.StateDelete, models.JobFinished, &StatusUpdater{sm.JobID, models.JobFinished})
}
